#include <iostream>
using std::cout;
using std::endl;
using std::cin;

#include <vector>
using std::vector;

#include <queue>
#include <functional>

#include "union_find.h"

struct edge
{
    int m_vertex1;
    int m_vertex2;
    int m_weight;
    bool m_sign; // 用于在Prim算法中标记该边是否进入过小根堆, 没有为false, 正在小根堆中或者进入过小根堆设为true

    edge(int vertex1 = 0, int vertex2 = 0, int weight = 0, bool sign = false)
        : m_vertex1(vertex1)
        , m_vertex2(vertex2)
        , m_weight(weight)
        , m_sign(sign)
    {

    }

    bool operator>(const edge& x) const
    {
        return m_weight > x.m_weight;
    }
};

// Prim
void solution()
{
    int n = 0; // 顶点数
    int e = 0; // 边数
    cin >> n >> e;
    
    vector<edge> edge_set(e + 1); // 边集
    vector<vector<edge*>> incidence_edge_set(n + 1); // 关联边集

    int vertex1 = 0;
    int vertex2 = 0;
    int weight = 0;
    for (int i = 1; i <= e; ++i)
    {
        cin >> vertex1 >> vertex2 >> weight;
        edge_set[i].m_vertex1 = vertex1;
        edge_set[i].m_vertex2 = vertex2;
        edge_set[i].m_weight = weight;

        incidence_edge_set[vertex1].push_back(&edge_set[i]);
        incidence_edge_set[vertex2].push_back(&edge_set[i]);
    }

    /*
        调试
    */
    // edge* tmp = nullptr;
    // for (int i = 1; i <= n; ++i)
    // {
    //     for (int j = 0; j < incidence_edge_set[i].size(); ++j)
    //     {
    //         tmp = incidence_edge_set[i][j];
    //     }
    // }

    // Prim算法的初始条件 1. 起始顶点 2. 初始可选择边集 3. 已生成子图
    const int start = 1;
    std::priority_queue<edge, vector<edge>, std::greater<edge>> min_heap;
    union_find subgraph(n);
    edge* cur = nullptr; 
    for (int i = 0; i < incidence_edge_set[start].size(); ++i)
    {
        cur = incidence_edge_set[start][i];
        min_heap.push(*cur);
        cur->m_sign = true;
    }

    // 开始计算最小生成树
    int selected_edge_count = 0;
    long long weight_sum = 0;
    while (!min_heap.empty() || selected_edge_count < n - 1)
    {
        edge edge_to_be_selected = min_heap.top();
        min_heap.pop();
        int subgraph_num1 = subgraph.find(edge_to_be_selected.m_vertex1);
        int subgraph_num2 = subgraph.find(edge_to_be_selected.m_vertex2);
        
        if (subgraph_num1 != subgraph_num2)
        {
            // 如果这条边添加之后不会构成环, 就添加这条边, 
            // 并且这也说明这条边触及了一个新的顶点, 还要将与新顶点相关联的边push到可选择边集min_heap中
            weight_sum += edge_to_be_selected.m_weight;
            ++selected_edge_count;

            int subgraph_num = subgraph.find(start);
            if (subgraph_num1 != subgraph_num)
            {
                subgraph.unite(subgraph_num, subgraph_num1);
                // 说明vertex1不在子图中
                for (int i = 0; i < incidence_edge_set[edge_to_be_selected.m_vertex1].size(); ++i)
                {
                    cur = incidence_edge_set[edge_to_be_selected.m_vertex1][i];
                    if (cur->m_sign == false)
                    {
                        min_heap.push(*cur);
                        cur->m_sign = true;
                    }
                }
            }
            else
            {
                subgraph.unite(subgraph_num, subgraph_num2);
                // 说明vertex2不在子图中
                for (int i = 0; i < incidence_edge_set[edge_to_be_selected.m_vertex2].size(); ++i)
                {
                    cur = incidence_edge_set[edge_to_be_selected.m_vertex2][i];
                    if (cur->m_sign == false)
                    {
                        min_heap.push(*cur);
                        cur->m_sign = true;
                    }
                }
            }
        }
        else
        {
            continue;
        }
    }

    cout << weight_sum << endl;
}

int main()
{
    solution();
    return 0;
}